import xlsx from 'xlsx';

const safeDecodeRange = (range) => {
    const o = {s: {c: 0, r: 0}, e: {c: 0, r: 0}};
    let idx;
    let i = 0;
    let cc = 0;
    const len = range.length;
    for (idx = 0; i < len; ++i) {
        if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
        idx = 26 * idx + cc;
    }
    o.s.c = --idx;

    for (idx = 0; i < len; ++i) {
        if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
        idx = 10 * idx + cc;
    }
    o.s.r = --idx;

    if (i === len || range.charCodeAt(++i) === 58) {
        o.e.c = o.s.c;
        o.e.r = o.s.r;
        return o;
    }

    for (idx = 0; i !== len; ++i) {
        if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
        idx = 26 * idx + cc;
    }
    o.e.c = --idx;

    for (idx = 0; i !== len; ++i) {
        if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
        idx = 10 * idx + cc;
    }
    o.e.r = --idx;
    return o;
};

const read = (data) => {
    const workbook = xlsx.read(data, {type: 'binary'});
    return workbook.Sheets[workbook.SheetNames[0]];
};

const toJson = (data, columns) => {
    const sheet = read(data);
    if (sheet === null || sheet['!ref'] === null) return [];
    if (columns === null || !Array.isArray(columns) || columns.size === 0) return [];
    let val = {t: 'n', v: 0};
    const header = 0;
    const offset = 1;
    const hdr = [];
    let isempty = true;
    let v = 0;
    const o = {};
    const raw = o.raw;
    const defval = o.defval;
    const range = sheet['!ref'];
    const r = safeDecodeRange(range);
    let rr = xlsx.utils.encode_row(r.s.r);
    const cols = [];
    const out = [];
    let outi = 0;
    let dense = Array.isArray(sheet);
    let R = r.s.r;
    let C;
    if (dense && !sheet[R]) sheet[R] = [];
    for (C = r.s.c; C <= r.e.c; ++C) {
        cols[C] = xlsx.utils.encode_col(C);
        hdr[C] = columns[C];
    }
    let row = (header === 1) ? [] : {};
    for (R = r.s.r + offset; R <= r.e.r; ++R) {
        rr = xlsx.utils.encode_row(R);
        isempty = true;
        if (header === 1) {
            row = [];
        } else {
            row = {};
            if (Object.defineProperty) {
                try {
                    Object.defineProperty(row, '__rowNum__', {value: R, enumerable: false});
                } catch (e) {
                    row.__rowNum__ = R;
                }
            } else {
                row.__rowNum__ = R;
            }
        }
        if (!dense || sheet[R]) {
            for (C = r.s.c; C <= r.e.c; ++C) {
                val = dense ? sheet[R][C] : sheet[cols[C] + rr];
                if (val === undefined || val.t === undefined) {
                    if (defval === undefined) continue;
                    if (hdr[C] !== null) {
                        row[hdr[C]] = defval;
                        isempty = false;
                    }
                    continue;
                }
                v = val.v;
                switch (val.t) {
                    case 'z':
                        if (v === null) break;
                        continue;
                    case 'e':
                        continue;
                    case 's':
                    case 'd':
                    case 'b':
                    case 'n':
                        break;
                    default:
                        throw new Error('unrecognized type ' + val.t);
                }
                if (hdr[C] !== null) {
                    if (v === null) {
                        if (defval !== undefined) row[hdr[C]] = defval;
                        else if (raw && v === null) row[hdr[C]] = null;
                        else continue;
                    } else {
                        row[hdr[C]] = raw ? v : xlsx.utils.format_cell(val, v, o);
                    }
                    isempty = false;
                }
            }
        }
        if ((isempty === false) || (header === 1 ? o.blankrows !== false : !!o.blankrows)) out[outi++] = row;
    }
    out.length = outi;
    return out;
};

export default {
    toJson: toJson
};
